﻿using System;
using System.Linq;
using System.Collections.Generic;
using gbL.NetBoard.Broadcast;

namespace gbL.NetBoard
{
    public class Blackboard : IBlackboard
    {
        public Blackboard()
        {
            Broadcaster = new Broadcaster();
            _taggedEntriesLock = new object();
            _taggedEntries = new Dictionary<object, IList<IBlackboardEntry>>();
        }

        public IFilteringBroadcaster Broadcaster { get; set; }

        public IBlackboardEntry Add(object message)
        {
            return Add(null, null, message);
        }

        public IBlackboardEntry Add(object from, object message)
        {
            return Add(null, from, message);
        }

        public IBlackboardEntry Add(object tag, object from, object message)
        {
            tag = tag ?? new object();
            IBlackboardEntry entry = BuildBlackboardEntry(tag, from, message);
            lock (_taggedEntriesLock)
            {
                if (!_taggedEntries.ContainsKey(tag))
                    _taggedEntries.Add(tag, BuildBlackboardEntryList());
                _taggedEntries[tag].Add(entry);
            }
            ChangedEventArgs args = new ChangedEventArgs(entry);
            Broadcaster.NotifyAdded(this, entry);
            return entry;
        }

        public IList<IBlackboardEntry> Read()
        {
            IList<IBlackboardEntry> ret = BuildBlackboardEntryList();
            lock (_taggedEntriesLock)
            {
                ret = _taggedEntries.SelectMany(x => x.Value).ToList();
            }
            return ret;
        }

        public IList<IBlackboardEntry> Read(Func<IBlackboardEntry, bool> selector)
        {
            IList<IBlackboardEntry> ret = BuildBlackboardEntryList();

            lock (_taggedEntriesLock)
            {
                foreach (IList<IBlackboardEntry> entries in _taggedEntries.Values)
                    foreach (IBlackboardEntry entry in entries.Where(e => selector(e)))
                        ret.Add(entry);
            }

            return ret;
        }

        public IList<IBlackboardEntry> ReadWithTag(object tag)
        {
            IList<IBlackboardEntry> ret = BuildBlackboardEntryList();

            lock (_taggedEntriesLock)
            {
                if (_taggedEntries.ContainsKey(tag))
                {
                    ret = _taggedEntries[tag];
                }
            }

            return ret;
        }

        public IList<IBlackboardEntry> Take(Func<IBlackboardEntry, bool> selector)
        {
            IList<IBlackboardEntry> ret = BuildBlackboardEntryList();

            lock (_taggedEntriesLock)
            {
                foreach (IList<IBlackboardEntry> entries in _taggedEntries.Values)
                {
                    var found = entries.Where(e => selector(e)).ToArray();
                    foreach (IBlackboardEntry entry in found)
                    {
                        ret.Add(entry);
                        entries.Remove(entry);
                    }
                }
                var emptyTags = _taggedEntries.Where(p => p.Value.Count == 0);
                foreach (object tag in emptyTags) _taggedEntries.Remove(tag);
            }

            Broadcaster.NotifyRemoved(this, ret.ToArray());

            return ret;
        }

        public IList<IBlackboardEntry> TakeWithTag(object tag)
        {
            IList<IBlackboardEntry> ret = BuildBlackboardEntryList();

            lock (_taggedEntriesLock)
            {
                if (_taggedEntries.ContainsKey(tag))
                {
                    ret = _taggedEntries[tag];
                    _taggedEntries.Remove(tag);
                }
            }

            Broadcaster.NotifyRemoved(this, ret.ToArray());

            return ret;
        }

        public IBlackboardEntry TakeEntry(IBlackboardEntry entry)
        {
            IBlackboardEntry ret = null;
            var taken = Take(x => entry.Equals(x));
            if (taken.Count > 0)
                ret = taken[0];
            return ret;
        }

        protected virtual IList<IBlackboardEntry> BuildBlackboardEntryList()
        {
            return new List<IBlackboardEntry>();
        }

        protected virtual IBlackboardEntry BuildBlackboardEntry(object tag, object from, object message)
        {
            return new BlackboardEntry(tag, from, message);
        }


        private readonly object _taggedEntriesLock;

        private Dictionary<object, IList<IBlackboardEntry>> _taggedEntries;
    }
}
